package com.atguigu.transaction;

import com.atguigu.util.JDBCUtils;
import org.junit.Test;

import java.lang.reflect.Field;
import java.sql.*;

/**
 * 1.什么叫数据库事务？
 * 事务：一组逻辑操作单元，使数据从一种状态变换到另一种状态
 * >一组逻辑操作单元：一个或多个 DML 操作
 * 2.事务处理的原则：保证所有事务都作为一个工作单元来执行，即使出现了故障，都不能改变这种执行方式。
 * 当在一个事务中执行多个操作时，要么所有的事务都被提交(commit)，那么这些修改就永久地保存
 * 下来；要么数据库管理系统将放弃所作的所有修改，整个事务回滚(rollback)到最初状态。
 * 3.数据一旦提交，就不可回滚
 * 4.哪些操作会导致数据的自动提交？
 * >DDL操作一旦执行，都会自动提交。 --> set autocommit = false 对DDL操作失效
 * >DML默认情况下，一旦执行，就会自动提交。 --> 我们可以通过set autocommit = false的方式取消DML操作的自动提交。
 * >默认在关闭连接时，会自动的提交数据
 *
 * @author admin
 */
public class TransactionTest {

    /**
     * 针对于数据表 user_table 来说，
     * AA 用户给 BB 用户转账100
     * update user_table set balance = balance - 100 where user = 'AA';
     * update user_table set balance = balance + 100 where user = 'BB';
     * ============================ 未考虑数据库事务转账操作 ==========================================
     */
    @Test
    public void testUpdate() throws Exception {
        String sql1 = "update user_table set balance = balance - 100 where user = ?";
        update(sql1, "AA");

        // 模拟网络异常
        System.out.println(10 / 0);

        String sql2 = "update user_table set balance = balance + 100 where user = ?";
        update(sql2, "BB");

        System.out.println("转账成功");
    }

    /**
     * 通用的增删改操作     ---> version 1.0
     * sql 中占位符的个数与可变形参的长度相同！
     */
    public int update(String sql, Object... args) throws Exception {

        Connection connection = null;
        PreparedStatement preparedStatement = null;

        try {
            // 1.获取数据库连接
            connection = JDBCUtils.getConnection();

            // 2.预编译 sql 语句，返回 PreparedStatement 实例对象
            preparedStatement = connection.prepareStatement(sql);

            // 3.填充占位符  小心参数声明错误！！！
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);
            }

            // 4.执行
            return preparedStatement.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 5.资源的关闭
            JDBCUtils.closeResource(connection, preparedStatement);
        }
        return 0;
    }

    /**
     * ===================================== 考虑数据库事务 ========================================
     */
    @Test
    public void testUpdateWithTx() {

        Connection connection = null;

        try {
            // 获取连接
            connection = JDBCUtils.getConnection();

            // 取消数据的自动提交
            connection.setAutoCommit(false);

            String sql1 = "update user_table set balance = balance - 100 where user = ?";
            update(connection, sql1, "AA");

            // 模拟网络异常
            System.out.println(10 / 0);

            String sql2 = "update user_table set balance = balance + 100 where user = ?";
            update(connection, sql2, "BB");

            System.out.println("转账成功");

            // 提交数据
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                connection.rollback();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        } finally {
            /*
             * 修改其为自动提交数据
             * 主要针对于使用数据库连接池的使用
             */
            try {
                connection.setAutoCommit(true);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            // 关闭资源
            JDBCUtils.closeResource(connection, null);
        }
    }

    /**
     * 通用的增删改操作     ---> version 2.0（考虑事务）
     * ============================ 考虑数据库事务转账操作 ===========================================
     */
    public int update(Connection connection, String sql, Object... args) throws Exception {

        PreparedStatement preparedStatement = null;

        try {
            // 1.预编译 sql 语句，返回 PreparedStatement 实例对象
            preparedStatement = connection.prepareStatement(sql);

            // 2.填充占位符  小心参数声明错误！！！
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);
            }

            // 3.执行
            return preparedStatement.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 4.资源的关闭
            JDBCUtils.closeResource(null, preparedStatement);
        }
        return 0;
    }

    /**
     * =============================================================================================
     */
    @Test
    public void testTransactionSelect() throws Exception {
        Connection connection = JDBCUtils.getConnection();

        // 获取当前连接的隔离级别
        System.out.println(connection.getTransactionIsolation());
        // 设置数据库的隔离级别
        connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        // 取消自动提交数据
        connection.setAutoCommit(false);

        String sql = "select user, password, balance from user_table where user = ?";
        User user = getInstance(connection, User.class, sql, "CC");
        System.out.println(user);
    }

    @Test
    public void testTransactionUpdate() throws Exception {
        Connection connection = JDBCUtils.getConnection();

        // 取消自动提交数据
        connection.setAutoCommit(false);

        String sql = "update user_table set balance = ? where user = ?";
        update(connection, sql, 4000, "CC");

        Thread.sleep(15000);
        System.out.println("修改结束");
    }

    /**
     * 针对于不同的表的通用的查询操作，返回表中的一条记录(Version 2.0 考虑事务)
     *
     * @param clazz
     * @param sql
     * @param args
     * @param <T>
     * @return
     */
    public <T> T getInstance(Connection connection, Class<T> clazz, String sql, Object... args) {
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            // 1.预编译 sql 语句，返回 PreparedStatement 实例
            preparedStatement = connection.prepareStatement(sql);

            // 2.填充占位符
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);
            }

            // 3.执行，并返回结果集
            resultSet = preparedStatement.executeQuery();
            // 获取结果集的元数据 ResultSetMetaData
            ResultSetMetaData rsmd = resultSet.getMetaData();
            // 通过 ResultSetMetaData 获取结果集中的列数
            int columnCount = rsmd.getColumnCount();

            // 4.处理结果集
            if (resultSet.next()) {
                T t = clazz.newInstance();

                // 处理结果集一行数据中的每一列
                for (int i = 0; i < columnCount; i++) {
                    // 获取列值
                    Object columnValue = resultSet.getObject(i + 1);

                    // 获取每个列的列名
                    String columnLabel = rsmd.getColumnLabel(i + 1);

                    // 给 customers 对象指定 columnName 属性，赋值为 columnValue   通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 5.关闭资源
            JDBCUtils.closeResource(null, preparedStatement, resultSet);
        }
        return null;
    }
}
